﻿/*=========================================================================
 * AUTH: Vincent Breton
 * DESC: Main Gameplay State
 *=========================================================================*/

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace RisingThreat
{
    class GameScreen:Screen
    {
        ContentManager content;
        GraphicsDeviceManager graphics;
        SpriteFont font;
        Matrix World, View, Projection;
        Vector3 cameraPosition;
        Vector3 cameraLookAt;
        Level level;
        FormationManager formation;
        List<Teammate> team;

        //List<Enemy[]> enemies = new List<Enemy[]>();
        //List<Enemy> enemies = new List<Enemy>();

        static public List<Enemy> enemies = new List<Enemy>();

        static public Player player;
        static public Camera camera;

        //Goal to win the level
        BoundingBox goalBounding;

        //coefficient of seperation aka 'bounce'
        float coeff = 0.1f;
                
        // A HUD instance
        private HUD hud;

        // TEMP: TEST VAR
        public const int PLAYER_MAX_LIFE = 100;
        int mode = 0;
        bool col = false;
        bool debug = false;

        // TEMP: TEST VAR
        public int LIFE = 100;

        KeyboardState PreviousKeyState;
        KeyboardState CurrentKeyState;
        float step = 0.025f; //speed of player 

        // Graphics effect
        Effect effect;

        // BGM (Song)
        private Song bgm;

        // Model team model
        private Model teamModel;

        // Model for enemies
        private Model enemyModel;

        //Model for bullets
        private Model bulletModel;
    
        public GameScreen(ContentManager cont, EventHandler gameEvent, GraphicsDeviceManager graphs)
            : base(gameEvent)
        {
            content = cont;
            
            graphics = graphs;
            camera = new Camera(graphs); 

            float aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
            /*World = Matrix.Identity;
            View = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up);
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 4/3, 1, 100);*/

            // TEMP: Init camera, etc...
            //World = Matrix.Identity;

            //View = Matrix.CreateLookAt(new Vector3(0, 70, 0), new Vector3(0, 0, 0), Vector3.Forward);

            //Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, graphics.GraphicsDevice.Viewport.AspectRatio, 1, 120);
            
            // Load text font
            font = content.Load<SpriteFont>("fonts/Cutscene");

            // Load graphics effect
            effect = content.Load<Effect>("Effect1");

            // INIT HUD:
            hud = new HUD(content, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
                           
            // Load enemy model
            teamModel = content.Load<Model>("Models/test");
            enemyModel = content.Load<Model>("Models/enemy");
            bulletModel = content.Load<Model>("Models/missile");

            //create player and team
            player = new Player(Vector3.Zero, teamModel, bulletModel);
            team = new List<Teammate>();

            // Load BGM
            bgm = content.Load<Song>("bgm");
            MediaPlayer.IsRepeating = true;
            MediaPlayer.Play(bgm);

            World = Matrix.Identity;
            camera.InitCamera(player);
            cameraPosition = camera.position;
            cameraLookAt = camera.lookAt;
            View = camera.cameraViewMatrix;
            Projection = camera.cameraProjectionMatrix;

            // INIT Level
            level = new Level(graphics, content, View, Projection, enemyModel, team, player);

            goalBounding = level.getGoalTile().getTile().getBoundingBox();
            goalBounding.Max.Y = goalBounding.Max.Y + 50.0f;

            player.steer = new Steering(level.getStartTile().getPosition() + Vector3.Up, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0f, 0f, 0f), 0, 0, 0, new Vector3(2.5f, 0, 2.5f), new Vector3(5.0f, 0.0f, 5.0f), 40.0f, 20.0f);
            team.Add(new Teammate(teamModel, new Vector3(player.steer.position.X - 2.0f, 0.5f, player.steer.position.Z - 2.0f), level.graph, bulletModel));
            team.Add(new Teammate(teamModel, new Vector3(player.steer.position.X + 2.0f, 0.5f, player.steer.position.Z - 2.0f), level.graph, bulletModel));

            formation = new FormationManager(player, team);
            // INIT enemies
            //enemies = new List<Enemy>();
            //enemies.Add(new Enemy(content.Load<Model>("Models/enemy"), new Vector3(0, 1.0f, -10.0f), team, player, level.graph));

        }


        public override void Update(GameTime gameTime)
        {
            float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

            checkAndProcessWin();
            tryLose();
            
            camera.UpdateCamera(player,level);
            cameraPosition = camera.position;
            cameraLookAt = camera.lookAt;
            View = camera.cameraViewMatrix;
            Projection = camera.cameraProjectionMatrix;

            if (mode == 1)
            {
                View = Matrix.CreateLookAt(new Vector3(0, 140, 0), new Vector3(0, 0, 0), Vector3.Forward);
            }

            // Process user input
            UpdateInput();


            // Player UPDATE
            player.update(gameTime, level);     

            // Update the level (also updates enemy generation)
            level.update(View, Projection, gameTime.ElapsedGameTime, player);
            
            //update formation
            formation.Update(level);

            // Move the team
           for (int i = 0; i < team.Count; i++)
            {
                team[i].update(gameTime, level);
            }

            // Get enemies
           enemies = level.getEnemies();

            // Update enemies
           foreach (Enemy enemy in enemies)
           {
                enemy.update(gameTime, level);
           }

           CheckAndResolveBulletCollisions();

            // COLLISIONS
            CheckAndResolveObjectCollsions();


            // Update the HUD
            hud.update(gameTime.ElapsedGameTime);

            // Update base class
            base.Update(gameTime);
       }

        public void CheckAndResolveBulletCollisions()
        {
            for (int i = 0; i != player.bullets.Length; i++)
            {
                for (int j = 0; j != enemies.Count; j++)
                {
                    if (player.bullets[i].isAlive && enemies[j].isAlive)
                    {
                        if (player.bullets[i].bounding.Intersects(enemies[j].bounding))
                        {
                            player.bullets[i].isAlive = false;
                            enemies[j].isAlive = false;
                        }
                    }
                }
            }
            foreach (Teammate teammate in team)
            {
                for (int i = 0; i != teammate.bullets.Length; i++)
                {
                    for (int j = 0; j != enemies.Count; j++)
                    {
                        if (teammate.bullets[i].isAlive && enemies[j].isAlive)
                        {
                            if (teammate.bullets[i].bounding.Intersects(enemies[j].bounding))
                            {
                                teammate.bullets[i].isAlive = false;
                                enemies[j].isAlive = false;
                            }
                        }
                    }
                }
            }

        }

        public void CheckAndResolveObjectCollsions()
        {
            for (int i = 0; i != team.Count - 1; i++)
            {
                for (int j = 1 + i; j != team.Count; j++)
                {
                    if (team[i].bounding.Intersects(team[j].bounding))
                    {
                        if (team[i].isAlive && team[j].isAlive)
                        {
                            Vector3 contactNorm = (team[i].steer.position - team[j].steer.position);
                            float dist = contactNorm.Length(); //for something else
                            contactNorm.Normalize();
                            //contact position is implied with intersecting spheres
                            //calculate seperation velocity
                            Vector3 seperationVelocity = (team[i].steer.velocity - team[j].steer.velocity);
                            seperationVelocity = seperationVelocity * contactNorm;
                            //use a coefficient of impulse
                            seperationVelocity *= coeff;
                            //apply impulse
                            team[i].steer.velocity += seperationVelocity;
                            team[j].steer.velocity -= seperationVelocity;
                            //added code to fix a bug making my bugs disapear with fast speed and small bugs
                            seperationVelocity.Normalize();
                            team[i].steer.position += 1.01f * seperationVelocity * (team[i].bounding.Radius - (dist / 2));
                            team[j].steer.position -= 1.01f * seperationVelocity * (team[j].bounding.Radius - (dist / 2));
                        }
                    }
                }
            }
            if (enemies.Count > 1)
            {
                for (int i = 0; i != enemies.Count - 1; i++)
                {
                    for (int j = 1 + i; j != enemies.Count; j++)
                    {
                        if (enemies[i].bounding.Intersects(enemies[j].bounding))
                        {
                            if (enemies[i].isAlive && enemies[j].isAlive)
                            {
                                Vector3 contactNorm = (enemies[i].steer.position - enemies[j].steer.position);
                                float dist = contactNorm.Length(); //for something else
                                contactNorm.Normalize();
                                //contact position is implied with intersecting spheres
                                //calculate seperation velocity
                                Vector3 seperationVelocity = (enemies[i].steer.velocity - enemies[j].steer.velocity);
                                seperationVelocity = seperationVelocity * contactNorm;
                                //use a coefficient of impulse
                                seperationVelocity *= coeff;
                                //apply impulse
                                enemies[i].steer.velocity += seperationVelocity;
                                enemies[j].steer.velocity -= seperationVelocity;
                                //added code to fix a bug making my bugs disapear with fast speed and small bugs
                                seperationVelocity.Normalize();
                                enemies[i].steer.position += 1.01f * seperationVelocity * (enemies[i].bounding.Radius - (dist / 2));
                                enemies[j].steer.position -= 1.01f * seperationVelocity * (enemies[j].bounding.Radius - (dist / 2));
                            }
                        }
                    }
                }
            }
            if (enemies.Count > 0)
            {
                for (int i = 0; i != team.Count; i++)
                {
                    for (int j = 0; j != enemies.Count; j++)
                    {
                        if (team[i].bounding.Intersects(enemies[j].bounding))
                        {
                            if (team[i].isAlive && enemies[j].isAlive)
                            {
                                Vector3 contactNorm = (team[i].steer.position - enemies[j].steer.position);
                                float dist = contactNorm.Length(); //for something else
                                contactNorm.Normalize();
                                //contact position is implied with intersecting spheres
                                //calculate seperation velocity
                                Vector3 seperationVelocity = (team[i].steer.velocity - enemies[j].steer.velocity);
                                seperationVelocity = seperationVelocity * contactNorm;
                                //use a coefficient of impulse
                                seperationVelocity *= coeff;
                                //apply impulse
                                team[i].steer.velocity += seperationVelocity;
                                enemies[j].steer.velocity -= seperationVelocity;
                                //added code to fix a bug making my bugs disapear with fast speed and small bugs
                                seperationVelocity.Normalize();
                                team[i].steer.position += 1.01f * seperationVelocity * (team[i].bounding.Radius - (dist / 2));
                                enemies[j].steer.position -= 1.01f * seperationVelocity * (enemies[j].bounding.Radius - (dist / 2));
                                team[i].steer.health -= 10;
                            }
                        }
                    }
                }
            }
            if (col)
            {
                for (int j = 0; j != team.Count; j++)
                {
                    if (player.bounding.Intersects(team[j].bounding))
                    {
                        if (team[j].isAlive)
                        {
                            Vector3 contactNorm = (player.steer.position - team[j].steer.position);
                            float dist = contactNorm.Length(); //for something else
                            contactNorm.Normalize();
                            //contact position is implied with intersecting spheres
                            //calculate seperation velocity
                            Vector3 seperationVelocity = (player.steer.velocity - team[j].steer.velocity);
                            seperationVelocity = seperationVelocity * contactNorm;
                            //use a coefficient of impulse
                            seperationVelocity *= coeff;
                            //apply impulse
                            //player.steer.velocity += seperationVelocity;
                            team[j].steer.velocity -= seperationVelocity;
                            //added code to fix a bug making my bugs disapear with fast speed and small bugs
                            seperationVelocity.Normalize();
                            //player.steer.position += 1.01f * seperationVelocity * (player.bounding.Radius - (dist / 2));
                            team[j].steer.position -= 1.01f * seperationVelocity * (team[j].bounding.Radius - (dist / 2));
                        }
                    }
                }
            }

            if (enemies.Count >= 1)
            {
                for (int j = 0; j != enemies.Count; j++)
                {
                    if (player.bounding.Intersects(enemies[j].bounding))
                    {
                        if (enemies[j].isAlive)
                        {
                            Vector3 contactNorm = (player.steer.position - enemies[j].steer.position);
                            float dist = contactNorm.Length(); //for something else
                            contactNorm.Normalize();
                            //contact position is implied with intersecting spheres
                            //calculate seperation velocity
                            Vector3 seperationVelocity = (player.steer.velocity - enemies[j].steer.velocity);
                            seperationVelocity = seperationVelocity * contactNorm;
                            //use a coefficient of impulse
                            seperationVelocity *= coeff;
                            //apply impulse
                            //player.steer.velocity += seperationVelocity;
                            enemies[j].steer.velocity -= seperationVelocity;
                            //added code to fix a bug making my bugs disapear with fast speed and small bugs
                            seperationVelocity.Normalize();
                            //player.steer.position += 1.01f * seperationVelocity * (player.bounding.Radius - (dist / 2));
                            enemies[j].steer.position -= 1.01f * seperationVelocity * (enemies[j].bounding.Radius - (dist / 2));
                            player.steer.health -= 5;
                        }
                    }
                }
            }
        }

        // Process user input
        public void UpdateInput()
        {
            PreviousKeyState = CurrentKeyState;
            CurrentKeyState = Keyboard.GetState();

            if (player.steer.health > 0)
            {
                if (CurrentKeyState.IsKeyDown(Keys.S))
                    player.steer.position += camera.camDir * step * new Vector3(1.0f, 0.0f, 1.0f);
                if (CurrentKeyState.IsKeyDown(Keys.W))
                    player.steer.position -= camera.camDir * step * new Vector3(1.0f, 0.0f, 1.0f);
                if (CurrentKeyState.IsKeyDown(Keys.A))
                    player.steer.position += camera.camNorm * step;
                if (CurrentKeyState.IsKeyDown(Keys.D))
                    player.steer.position -= camera.camNorm * step;
                if (CurrentKeyState.IsKeyDown(Keys.Q))
                    player.steer.orientation += 0.02f;
                if (CurrentKeyState.IsKeyDown(Keys.E))
                    player.steer.orientation -= 0.02f;

                if (CurrentKeyState.IsKeyDown(Keys.F) && PreviousKeyState.IsKeyUp(Keys.F))
                {
                    //shoot
                    player.Shoot();
                }
            }

            if (CurrentKeyState.IsKeyDown(Keys.Space) && PreviousKeyState.IsKeyUp(Keys.Space))
            {
                if (mode == 0)
                    mode = 1;
                else
                    mode = 0;
            }

            if (CurrentKeyState.IsKeyDown(Keys.M) && PreviousKeyState.IsKeyUp(Keys.M))
            {
                if (debug)
                    debug = false;
                else
                    debug = true;
            }

            if (CurrentKeyState.IsKeyDown(Keys.Escape))
            {
                PauseGame();
            }

            if (debug)
            {

                if (CurrentKeyState.IsKeyDown(Keys.Up))
                    step += 0.01f;
                if (CurrentKeyState.IsKeyDown(Keys.Down))
                    step -= 0.01f;

                if (CurrentKeyState.IsKeyDown(Keys.LeftShift) && PreviousKeyState.IsKeyUp(Keys.LeftShift))
                {
                    int i = 0; //put a break here when testing to have a 'break key'
                }                

                if (CurrentKeyState.IsKeyDown(Keys.B) && PreviousKeyState.IsKeyUp(Keys.B))
                {
                    if (col)
                        col = false;
                    else
                        col = true;
                }
                

                if (CurrentKeyState.IsKeyDown(Keys.L))
                {
                    --LIFE;
                    if (LIFE < 0)
                    {
                        LIFE = 0;
                    }
                }

                // TEST
                if (CurrentKeyState.IsKeyDown(Keys.LeftAlt) && PreviousKeyState.IsKeyUp(Keys.LeftAlt))
                {
                    this.loadNextLevel();
                }

            }
        }

        // Pause the game, invoke PAUSE state
        private void PauseGame()
        {
            screenEvent.Invoke(this, new ScreenArgs(0));
           MediaPlayer.Pause();
        }

        // Go to tryGameOver
        private void tryLose()
        {
            if (player.steer.health <= 0)
            {
                MediaPlayer.Stop();

                // HACK
                LevelGraphNode.ID_COUNTER = 0;

                screenEvent.Invoke(this, new ScreenArgs(1));
            }
        }

        private void checkAndProcessWin()
        {
            if (player.bounding.Intersects(goalBounding))
            {
                //Add a congradulations screen?
                this.loadNextLevel();
                //add an overall victory case?
            }
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            // Clear screen
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // Set culling to NONE
            RasterizerState rasterizerState1 = new RasterizerState();
            rasterizerState1.CullMode = CullMode.None;
            graphics.GraphicsDevice.RasterizerState = rasterizerState1;

            // Enable depth buffering
            graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            // Set effect
            effect.CurrentTechnique = effect.Techniques["Simplest"];
            effect.Parameters["xViewProjection"].SetValue(View * Projection);

            // Draw the Level Geometry
            level.draw(graphics, effect, View, Projection);

            player.Draw(graphics, View, Projection);

            for (int i = 0; i < team.Count; i++)
            {
                team[i].Draw(graphics, View, Projection);
            }

            // Draw enemies
            foreach (Enemy enemy in enemies)
            {
                enemy.Draw(graphics, View, Projection);
            }

            foreach (Bullet bullet in player.bullets)
            {
                bullet.Draw(graphics, View, Projection);
            }

            foreach (Teammate teammate in team)
            {
                foreach (Bullet bullet in teammate.bullets)
                {
                    bullet.Draw2(graphics, View, Projection);
                }
            }

            // Draw the HUD
            hud.draw(spriteBatch, player.steer.health);

            // Draw base class
            base.Draw(spriteBatch);
        }

        // Pause music
        private void pauseMusic()
        {
            MediaPlayer.Pause();
        }
        // Play music
        public void playMusic()
        {
            MediaPlayer.Resume();
        }

        // Load next level and reset players/enemies
        private void loadNextLevel()
        {
            // Load next level
            level.loadNextLevel(graphics, content, View, Projection, enemyModel, team, player);

            // Reset players
            resetPlayers();

            // Reset spawn points
            level.resetSpawnPoints();

            //set new goal point
            goalBounding = level.getGoalTile().getTile().getBoundingBox();
            goalBounding.Max.Y = goalBounding.Max.Y + 50.0f;

            //reset health
            player.steer.health = 100;
            player.isAlive = true;
        }

        // RESET players, teammates, and enemies
        private void resetPlayers()
        {
            // Set player
            player.steer.position = level.getStartTile().getPosition() + Vector3.Up;

            // Set teammates
            team[0].steer.position = new Vector3(player.steer.position.X - 2.0f, 0.0f, player.steer.position.Z - 2.0f);
            team[1].steer.position = new Vector3(player.steer.position.X + 2.0f, 0.0f, player.steer.position.Z - 2.0f);

            // Destroy enemies
            enemies.Clear();
        }
    }
}
